----------------------------------------------------------------------------------- -- Monster binding ----------------------------------------------------------------------------------- function bind(obj) obj:bind_object(generic_object_binder(obj)) end local last_update = 0 -- combat local snd_polter_death = {} ------------------------------------------------------------------------------------ class "generic_object_binder" (object_binder) function generic_object_binder:__init(obj) super(obj) self.loaded = false end function generic_object_binder:reinit() --utils_data.debug_write(self.object:name().."re_init() Start") object_binder.reinit(self) self.st = {} db.storage[self.object:id()] = self.st if (self.object:alive()) then self.object:set_callback(callback.patrol_path_in_point, self.waypoint_callback, self) self.object:set_callback(callback.hit, self.hit_callback, self) self.object:set_callback(callback.death, self.death_callback, self) self.object:set_callback(callback.sound, self.hear_callback, self) self.object:set_tip_text("") end if (USE_MARSHAL) then RegisterScriptCallback("save_state",self) end --utils_data.debug_write(self.object:name().."re_init() End") end function generic_object_binder:use_kick(obj, who) local pos = who:position() local h = hit() h.power = 1 h.direction = obj:position():sub(vector():set(pos.x,pos.y+0.3,pos.z)) h.bone = "bip01_spine" h.draftsman = who h.impulse = 1000 --6000 h.type = hit.wound obj:hit(h) end function generic_object_binder:update(delta) --utils_data.debug_write(strformat("%s:update loaded=%s",self.object and self.object:name(),self.loaded)) object_binder.update(self, delta) -- Start [Alundaio] SendScriptCallback("monster_on_update",self.object,self.st) -- End [Alundaio] -- Faustle if not (self.object:alive()) then local looted = se_load_var(self.object:id(),self.object:name(),"looted") if (not looted) then if (item_knife.is_equipped()) then if (item_knife.get_condition() < 0.15) then self.object:set_callback(callback.use_object, nil) self.object:set_tip_text(game.translate_string("st_body_knife_bad")) return end if (not item_knife.can_loot(self.object:section())) then self.object:set_callback(callback.use_object, nil) self.object:set_tip_text(game.translate_string("st_body_knife_weak")) else self.object:set_callback(callback.use_object, self.use_callback, self) self.object:set_tip_text(game.translate_string("st_body_loot")) end else self.object:set_callback(callback.use_object, nil) self.object:set_tip_text(game.translate_string("st_body_knife_needed")) return end else self.object:set_callback(callback.use_object, nil) self.object:set_tip_text("") end return end -- local id = self.object:id() local sim = alife() local se_obj = sim:object(id) if not (se_obj) then return end local cls = self.object:clsid() if (cls == clsid.tushkano_s or cls == clsid.rat_s) then self.object:set_callback(callback.use_object, self.use_kick, self) self.object:set_tip_text(self.object:set_tip_text(game.translate_string("st_body_kick"))) end if self.st ~= nil and self.st.active_scheme ~= nil then xr_logic.try_switch_to_another_section(self.object, self.st[self.st.active_scheme], db.actor) end local squad = se_obj.group_id and se_obj.group_id ~= 65535 and sim:object(se_obj.group_id) if (squad and squad:commander_id() == self.object:id()) then squad:update() end if self.object:get_enemy() then xr_logic.mob_release(self.object) return else xr_combat_ignore.fighting_with_actor_npcs[id] = nil end if self.st.active_section ~= nil then xr_logic.issue_event(self.object, self.st[self.st.active_scheme], "update", delta) end if (squad and squad.current_action and squad.current_action == 0) then local squad_target = squad.assigned_target_id and simulation_objects.get_server_entity(squad.assigned_target_id) if not (squad_target) then xr_logic.mob_release(self.object) return end if not (simulation_objects.is_on_the_same_level(squad_target, se_obj)) then TeleportSquad(squad,squad_target.position,squad_target.m_level_vertex_id,squad_target.m_game_vertex_id) return end xr_logic.mob_capture(self.object, true) local commander = db.storage[squad:commander_id()] and db.storage[squad:commander_id()].object if (commander == nil or commander:id() == id) then action(self.object, move(move.walk_with_leader, squad_target.position or self.object:position()),cond(cond.move_end)) else local pos = commander:position() if (pos:distance_to(self.object:position()) > 10) then action(self.object, move(move.run_with_leader, pos),cond(cond.move_end)) else action(self.object, move(move.walk_with_leader, pos),cond(cond.move_end)) end end end end function generic_object_binder:extrapolate_callback() --utils_data.debug_write(strformat("%s:extrapolate_callback START",self.object and self.object:name())) -- Проверяем, что объект еще в онлайне local st = db.storage[self.object:id()] if (st == nil or st.object == nil) then --utils_data.debug_write(strformat("%s:extrapolate_callback end",self.object and self.object:name())) return end local cur_pt = self.object:get_current_point_index() if self.object:get_script() == false then --utils_data.debug_write(strformat("%s:extrapolate_callback end",self.object and self.object:name())) return false end local patrol_path = self.object:patrol() if not level.patrol_path_exists(patrol_path) then --utils_data.debug_write(strformat("%s:extrapolate_callback end",self.object and self.object:name())) return false end if patrol(patrol_path):flags(cur_pt):get() == 0 then --utils_data.debug_write(strformat("%s:extrapolate_callback end",self.object and self.object:name())) return true end --utils_data.debug_write(strformat("%s:extrapolate_callback end",self.object and self.object:name())) return false end function generic_object_binder:waypoint_callback(obj, action_type, index) if self.st.active_section ~= nil then xr_logic.issue_event(self.object, self.st[self.st.active_scheme], "waypoint_callback", obj, action_type, index) end end function generic_object_binder:death_callback(victim, who) --utils_data.debug_write(strformat("%s:death_callback killer=%s",victim and victim:name(),who and who:name())) local id = self.object:id() xr_combat_ignore.fighting_with_actor_npcs[id] = nil db.delete_enemy(victim) -- This is necessary because hit callback is not triggered if it takes a single blow to kill npc self:hit_callback(victim, 1, VEC_ZERO, who, "from_death_callback") if (who) then if (who:id() == AC_ID) then game_statistics.increment_statistic("killed_monsters") elseif (IsStalker(who) and who:alive()) then game_statistics.increment_npc_statistic(who,"killed_monsters") end end if self.st.mob_death then xr_logic.issue_event(victim, self.st.mob_death, "death_callback", victim, who) end if self.st.active_section then xr_logic.issue_event(victim, self.st[self.st.active_scheme], "death_callback", victim, who) end victim:set_callback(callback.death,nil) victim:set_callback(callback.patrol_path_in_point,nil) victim:set_callback(callback.hit,nil) victim:set_callback(callback.sound,nil) if (victim:clsid() == clsid.poltergeist_s) then -- Tronex: death sounds if not (snd_polter_death[1] and snd_polter_death[2]) then snd_polter_death[1] = sound_object("monsters\\poltergeist\\death_0") snd_polter_death[2] = sound_object("monsters\\poltergeist\\death_1") end snd_polter_death[math.random(1,2)]:play_at_pos(victim, victim:position()) if who and (who:id() == AC_ID) then local kind_to_section = { ["SM_POLTER_G"] = "poltergeist", ["SM_PYRO_G"] = "pyrogeist", ["SM_PSEUDO_G"] = "pseudogeist" } local sec = victim:section() local kind = ini_sys:r_string_ex(sec,"kind") or "unknown" local name = kind_to_section[kind] if name then SendScriptCallback("actor_on_interaction", "mutants", nil, name) end end -- Tronex local se_obj = alife_object(id) if (se_obj) then safe_release_manager.release(se_obj) end else release_body_manager.get_release_body_manager():moving_dead_body(victim) -- Alundaio SendScriptCallback("monster_on_death_callback",victim,who) self.st.death_time = game.get_game_time() self.st.death_by_id = who:id() -- End Alundaio end end function generic_object_binder:hit_callback(obj, amount, local_direction, who, bone_index) --utils_data.debug_write(strformat("generic_object_binder %s",obj and obj:name())) if (amount <= 0) then return end if self.st.hit then xr_logic.issue_event(obj, self.st.hit, "hit_callback", obj, amount, local_direction, who, bone_index) end -- Alundaio SendScriptCallback("monster_on_hit_callback",obj,amount,local_direction,who,bone_index) -- End Alundaio end function generic_object_binder:hear_callback(npc, who_id, sound_type, sound_position, sound_power) if not (npc:alive()) then return end if who_id == npc:id() then return end xr_hear.hear_callback(npc, who_id, sound_type, sound_position, sound_power) end function generic_object_binder:use_callback(obj, who) if not (self.object) then return end -- Alundaio SendScriptCallback("monster_on_actor_use_callback",obj,who) -- Alundaio end function generic_object_binder:net_spawn(se_abstract) --utils_data.debug_write("\n----"..se_abstract:name()..":net_spawn()----",true) if not object_binder.net_spawn(self, se_abstract) then --utils_data.debug_write(se_abstract:name()..":net_spawn() FAILURE FAILURE object_binder.net_spawn returned false!!!") return false end local se_obj = alife_object(self.object:id()) if not (se_obj) then return false end -- Alundaio SendScriptCallback("monster_on_net_spawn",self.object,se_obj) -- Alundaio db.add_obj(self.object) if not self.object:alive() then if (self.object:clsid() == clsid.poltergeist_s) then safe_release_manager.release(se_obj) return true end --utils_data.debug_write(se_obj:name()..":net_spawn() Moving dead body...") release_body_manager.get_release_body_manager():moving_dead_body(self.object,true) --utils_data.debug_write(se_obj:name()..":net_spawn() Moving dead body done") --utils_data.debug_write(se_obj:name()..":net_spawn() END") -- fix for 'Invalid vertex for object' if not (se_obj.m_level_vertex_id < 4294967295 and self.object:accessible(se_obj.m_level_vertex_id)) then printf("npc %s has invalid spawning vertex, correcting",se_obj:name()) local v_id = self.object:accessible_nearest(level.vertex_position(se_obj.m_level_vertex_id), VEC_ZERO) self.object:set_npc_position(level.vertex_position(v_id)) end self.already_dead = true return true end db.add_enemy(self.object) smart_terrain.setup_gulag_and_logic_on_spawn(self.object, self.st, se_obj, modules.stype_mobile, self.loaded) local force_set_position if (db.offline_objects[se_obj.id] and db.offline_objects[se_obj.id].level_vertex_id) then force_set_position = level.vertex_position(db.offline_objects[se_obj.id].level_vertex_id) db.offline_objects[se_obj.id].level_vertex_id = nil elseif db.spawned_vertex_by_id[se_obj.id] ~= nil then force_set_position = level.vertex_position(db.spawned_vertex_by_id[se_obj.id]) db.spawned_vertex_by_id[se_obj.id] = nil end if (force_set_position) then self.object:set_npc_position(force_set_position) end --utils_data.debug_write(se_obj:name()..":net_spawn() END") return true end function generic_object_binder:net_destroy() --utils_data.debug_write("generic_object_binder:net_destroy START") self.object:set_callback(callback.death,nil) self.object:set_callback(callback.patrol_path_in_point,nil) self.object:set_callback(callback.hit,nil) self.object:set_callback(callback.sound,nil) self.object:set_callback(callback.use_object,nil) xr_sound.stop_sounds_by_id(self.object:id()) SendScriptCallback("monster_on_net_destroy",self.object) xr_combat_ignore.fighting_with_actor_npcs[self.object:id()] = nil if (self.st) then if (self.st.active_scheme) then xr_logic.issue_event(self.object, self.st[self.st.active_scheme], "net_destroy", self.object) end if (self.st.overrides and self.st.overrides.on_offline_condlist) then xr_logic.pick_section_from_condlist(db.actor, self.object, self.st.overrides.on_offline_condlist) end end -- Запоминаем позицию и активную секцию -------- if db.offline_objects[self.object:id()] then db.offline_objects[self.object:id()].level_vertex_id = self.object:level_vertex_id() db.offline_objects[self.object:id()].active_section = db.storage[self.object:id()].active_section end if (USE_MARSHAL) then UnregisterScriptCallback("save_state",self) end db.del_obj(self.object) db.delete_enemy(self.object) local m_data = alife_storage_manager.get_state() if (m_data.game_object) then m_data.game_object[self.object:id()] = nil end object_binder.net_destroy(self) --utils_data.debug_write("generic_object_binder:net_destroy END") end function generic_object_binder:reload(section) --printf("start generic_object_binder:reload(): self.object:name()='%s' section=%s", self.object:name(),section) --utils_data.debug_write(strformat("--------%s:reload START",self.object and self.object:name())) object_binder.reload(self, section) --utils_data.debug_write(strformat("--------%s:reload END",self.object and self.object:name())) --printf("end generic_object_binder:reload(): self.object:name()='%s' section=%s", self.object:name(),section) end function generic_object_binder:net_save_relevant() --printf("generic_object_binder:net_save_relevant(): self.object:name()='%s'", self.object:name()) return true end function generic_object_binder:save(packet) set_save_marker(packet, "save", false, "generic_object_binder") object_binder.save(self, packet) set_save_marker(packet, "save", true, "generic_object_binder") if (USE_MARSHAL) then return end --utils_data.debug_write("\n-------bind_monster save start------ "..self.object:name(),true) xr_logic.save_obj(self.object, packet) --utils_data.debug_write("\n-------bind_monster save END----------"..self.object:name(),true) end function generic_object_binder:load(reader) self.loaded = true set_save_marker(reader, "load", false, "generic_object_binder") object_binder.load(self, reader) set_save_marker(reader, "load", true, "generic_object_binder") if (USE_MARSHAL) then if (self.object) then -- ?? self:load_state() end return end --utils_data.debug_write("\n-------bind_monster load start------ "..self.object:name(),true) xr_logic.load_obj(self.object, reader) --utils_data.debug_write("\n--------bind monster load END -------"..self.object:name()) end ----------------------------------------------------------------------------------------------------------------- function generic_object_binder:save_state(m_data) --utils_data.debug_write(strformat("generic_object_binder:save_state %s BEFORE",self.object:name())) local id = self.object and self.object:id() local st = id and db.storage[id] if not (st and m_data) then return end local state = alife_storage_manager.get_game_object_state(self.object,true) state.xr_logic = empty_table(state.xr_logic) --state.xr_logic.job_ini = st.job_ini state.xr_logic.ini_filename = st.ini_filename state.xr_logic.section_logic = st.section_logic state.xr_logic.active_section = st.active_section state.xr_logic.gulag_name = st.gulag_name state.xr_logic.activation_time = (st.activation_time or 0) - time_global() state.xr_logic.activation_game_time = st.activation_game_time db.storage[id].pstor = db.storage[id].pstor or {} --utils_data.debug_write(strformat("generic_object_binder:save_state %s AFTER",self.object:name())) end function generic_object_binder:load_state() local state = alife_storage_manager.get_game_object_state(self.object) if not (state and state.xr_logic) then return end local id = self.object:id() db.storage[id] = db.storage[id] or {} local st = db.storage[id] --utils_data.debug_write(strformat("generic_object_binder:load_state %s BEFORE",self.object:name())) --st.job_ini = state.xr_logic.job_ini st.loaded_ini_filename = state.xr_logic.ini_filename st.loaded_section_logic = state.xr_logic.section_logic st.loaded_active_section = state.xr_logic.active_section st.loaded_gulag_name = state.xr_logic.gulag_name or "" st.activation_time = state.xr_logic.activation_time and state.xr_logic.activation_time + time_global() or time_global() st.activation_game_time = state.xr_logic.activation_game_time or game.get_game_time() if (state.pstor_all) then db.storage[id].pstor = state.pstor_all state.pstor_all = nil end if (state.pstor_ctime) then db.storage[id].pstor_ctime = state.pstor_ctime state.pstor_ctime = nil end --utils_data.debug_write(strformat("generic_object_binder:load_state %s AFTER",self.object:name())) end